#!/usr/bin/python3 -I
# The `-I` flag enables isolation, which disables environment variables that
# change the behavior of the interpreter like `PYTHONPATH`, preventing
# privilege escalation (as this script is meant to be executed through sudo).

from sys import argv, stdin, stderr
import jwt
import os
import requests
import time


def generate_jwt():
    base = os.path.dirname(os.path.realpath(__file__))
    with open('/etc/github-app-credentials/app_id.txt', 'r') as fh:
        app_id = int(fh.read())
    with open('/etc/github-app-credentials/app_private_key.pem', 'rb') as fh:
        private_key = fh.read()

    now = int(time.time())
    payload = {
        "iat": now - 60,
        "exp": now + (60 * 9),
        "iss": app_id,
    }

    token = jwt.encode(payload, private_key, algorithm="RS256")
    if type(token) == bytes:
        token = token.decode("ascii")
    return token


def generate_token(user, repo):
    token = generate_jwt()

    def req(method, url):
        return requests.request(method, f"https://api.github.com/{url}", headers={
            "Authorization": f"Bearer {token}",
        })

    installation = req("GET", f"repos/{user}/{repo}/installation")
    if installation.status_code == 404:
        # 404 could be caused either by the app not being installed at all on the account,
        # or by the repository not being authorized with the app. Try to retrieve the
        # installation in the user's account to check which case we're handling.
        user_installation = req("GET", f"users/{user}/installation")
        if user_installation.status_code == 200:
            raise RepositoryNotAuthorizedError(user_installation.json()["html_url"])
        else:
            # Requesting the app is required to get the app URL.
            app = req("GET", "app")
            app.raise_for_status()
            raise ApplicationNotInstalledError(app.json()["html_url"])
    installation.raise_for_status()
    installation = installation.json()

    auth = req("POST", f"app/installations/{installation['id']}/access_tokens")
    if auth.status_code == 403:
        raise SuspendedInstallationError(installation["html_url"])
    auth.raise_for_status()
    auth = auth.json()

    return auth["token"]


def credential_provider():
    if argv[1] != "get":
        exit(0)

    config = dict(x.strip().split("=", 1) for x in stdin)

    if "host" not in config or config["host"] != "github.com":
        exit(0)
    git_user, repo = config["path"].split("/", 1)
    if repo.endswith(".git"):
        repo = repo[:-len(".git")]

    # Get the username of the user who executed sudo. This is safe because
    # unprivileged users can't execute this script directly, and even if they
    # somehow manage to execute the script they won't have permissions to
    # access the private key used to generate tokens.
    #
    # Also, it's not possible to override SUDO_USER as the unprivileged user,
    # even when preserving the environment (which is disabled anyway).
    user = os.environ["SUDO_USER"]
    if user.startswith("gh-"):
        user = user[3:]

    # we don't support usernames differing from repo owner names for now
    if user != git_user:
        exit(0)

    for key, value in config.items():
        print(f"{key}={value}")

    try:
        password = generate_token(user, repo)

        print(f"username={user}")
        print(f"password={password}")
        print()
    except ApplicationNotInstalledError as e:
        log("error: failed to obtain git credentials")
        log()
        log("In order to push from dev-desktop instances you need to install the")
        log("GitHub App and give it access to the repositories you want to push to:")
        log()
        log(f"    {e.app_url}")
        log()
        print("quit=true")
    except SuspendedInstallationError as e:
        log("error: failed to obtain git credentials")
        log()
        log("You suspended the installation of the GitHub App. Please re-enable")
        log("it in order to authenticate with GitHub from dev-desktop machines:")
        log()
        log(f"    {e.installation_url}")
        log()
        print("quit=true")
    except RepositoryNotAuthorizedError as e:
        log("error: failed to obtain git credentials")
        log()
        log("You installed the GitHub App in your account, but you did not authorize")
        log(f"it to access the {user}/{repo} repository. You can change that here:")
        log()
        log(f"    {e.installation_url}")
        log()
        print("quit=true")


def log(message=""):
    print(message, file=stderr)


class ApplicationNotInstalledError(RuntimeError):
    def __init__(self, app_url):
        self.app_url = app_url


class SuspendedInstallationError(RuntimeError):
    def __init__(self, installation_url):
        self.installation_url = installation_url


class RepositoryNotAuthorizedError(RuntimeError):
    def __init__(self, installation_url):
        self.installation_url = installation_url


if __name__ == "__main__":
    credential_provider()
